Swadhin Biswas Feature/ai code reviewer (#73) · 8/2/2026
105a287
122 lines · 4.0 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122

import type { APIRoute } from "astro";
import { handleReceivePack } from "@/lib/git-server";
import { acquireRepo, releaseRepo, getStorageRepoPath } from "@/lib/git-storage";
import { logger } from "@/lib/logger";
import { getDatabase, schema } from "@/db";
import { validateBasicAuth } from "@/lib/auth-basic";
import { canWriteRepo } from "@/lib/permissions";
import { and, eq } from "drizzle-orm";

export const POST: APIRoute = async ({ params, request }) => {
    const { owner, repo } = params;

    if (!owner || !repo) {
        return new Response("Missing owner or repo", { status: 400 });
    }

    // Handle .git suffix
    const repoName = repo.endsWith(".git") ? repo.slice(0, -4) : repo;

    // Check Content-Type
    const contentType = request.headers.get("Content-Type");
    if (contentType !== "application/x-git-receive-pack-request") {
        return new Response("Invalid Content-Type", { status: 415 });
    }

    // Authenticate user
    const authHeader = request.headers.get("Authorization");
    let userId: string | null = null;
    if (authHeader) {
        userId = await validateBasicAuth(authHeader);
    }

    // Find repo and check permissions
    const db = getDatabase();
    const ownerUser = await db.query.users.findFirst({
        where: eq(schema.users.username, owner),
    });

    if (!ownerUser) {
        return new Response("Repository not found", { status: 404 });
    }

    const repoData = await db.query.repositories.findFirst({
        where: and(
            eq(schema.repositories.ownerId, ownerUser.id),
            eq(schema.repositories.name, repoName)
        ),
    });

    if (!repoData) {
        return new Response("Repository not found", { status: 404 });
    }

    // Check write permission
    const user = userId ? await db.query.users.findFirst({
        where: eq(schema.users.id, userId),
    }) : null;
    const isAdmin = user?.isAdmin === true;
    const hasAccess = await canWriteRepo(userId || undefined, repoData, { isAdmin });

    if (!hasAccess) {
        return new Response("Unauthorized", {
            status: 401,
            headers: { "WWW-Authenticate": 'Basic realm="OpenCodeHub"' },
        });
    }

    logger.info({ owner, repoName }, "Git receive-pack request");

    let repoPath: string;
    try {
        repoPath = await acquireRepo(owner, repoName);
    } catch (err) {
        logger.error({ err }, "Failed to acquire repo");
        return new Response("Repository not found", { status: 404 });
    }

    const storagePath = getStorageRepoPath(owner, repoName);

    // Extract user from Basic Auth if present
    let remoteUser = "git";
    if (authHeader && authHeader.startsWith("Basic ")) {
        try {
            const buffer = Buffer.from(authHeader.slice(6), "base64");
            const creds = buffer.toString("utf-8");
            const [user] = creds.split(":");
            if (user) remoteUser = user;
        } catch (e) {
            // Ignore auth parse errors
        }
    }

    try {
        // Process the pack stream
        const responseStream = await handleReceivePack(repoPath, request.body!, storagePath, {
            REMOTE_USER: remoteUser
        });

        // Return response first, then sync to cloud storage in background
        // This prevents the S3 upload from blocking the git client response
        const response = new Response(responseStream as any, {
            headers: {
                "Content-Type": "application/x-git-receive-pack-result",
                "Cache-Control": "no-cache",
            },
        });

        // Sync to cloud storage in background (non-blocking)
        releaseRepo(owner, repoName, true).catch(err => {
            logger.error({ err }, "Failed to sync repo to storage after push");
        });

        return response;
    } catch (err) {
        logger.error({ err }, "Failed to handle receive-pack");
        // Release repo without saving changes (rollback attempt)
        await releaseRepo(owner, repoName, false);
        return new Response("Internal Server Error", { status: 500 });
    }
};